/**
 * Clase que busca y cuenta palabras en uno o multiples documentos de texto.
 * 
 * @author HPCIII_Team2013
 */

package cl.ucn.hpc.procesamiento;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

import cl.ucn.hpc.wordCounter.ConcreteQueueWordCounter;
import cl.ucn.hpc.wordCounter.IQueueWordProcessing;
import cl.ucn.hpc.wordCounter.WordCounterWork;

public class WordProcessing implements Runnable{
	private String filepath;
	private String URL;
	private int opcion;
	private int maxCantWordCounterThread;
	private LinkedBlockingQueue <String> resultado;
	private LinkedBlockingQueue <Integer> finishedWorks;
	private int tiempoEspera;
	private int factorDeDivision;
	
	//Tag que indica si todos los trabajos se han realizado y se tiene el resultado final.
	private boolean ready;
	
	/**
	 * Constructor de la clase.
	 * 
	 * @param pathRec Directorio general donde debe almacenar la informacion recibida.
	 * @param resultado Lista bloqueante en la cual se encuentra el resultado.
	 */
	public WordProcessing(String filepath, LinkedBlockingQueue<String> resultado){
		this.URL = "";
		this.filepath = filepath;
		this.resultado = resultado;
		this.maxCantWordCounterThread = 30;
		this.ready = false;
		this.tiempoEspera = 300;
		
	}
	
	/**
	 * Determina la cantidad de wordCounterThreads que debiesen estar trabajando sobre la lista de trabajos.
	 * Se asume que en promedio cada wordCounterThread se demora 0,02 segundos en procesar una pagina.
	 * De esta forma, se determina la cantidad necesaria para procesar todos los trabajos pendientes en un segundo.
	 * Esto sin exceder la maximo de wordCounterThreads propuesto.
	 * 
	 * @param tamLista Tamanio de la lista de trabajos a procesar.
	 * @return La cantidad de wordCounterThreads a crear.
	 */
	private int calcularWordCounterThreadsNecesarios(int tamLista) {
		int cantWordCounterThreads = tamLista/this.factorDeDivision +1;
		
		
		if(cantWordCounterThreads <= this.maxCantWordCounterThread){
			return cantWordCounterThreads;
		}else{
			return this.maxCantWordCounterThread;
		}						
	}
	
	/**
	 * Retorna la cantidad de URLs almacenadas hasta el momento.
	 * 
	 * @return numero de URLs almacenadas actualmente.
	 * @throws IOException Si no se encuentra el archivo.
	 */
	protected int countStoredUrls() throws IOException{
		BufferedReader buf = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(filepath+ File.separatorChar +"0pagsRegist.txt"))));
	    @SuppressWarnings("unused")
		String line;
	    int lineNumber = 0;
	    while ((line = buf.readLine()) != null)   {
	        lineNumber++;
	    }
	    buf.close();
	    return lineNumber;
	}
	
	/**
	 * Coordina el trabajo de los wordCounterThreads.
	 * Les asigna las paginas sobre las que deben trabajar.
	 * Cada cierto numero de paginas revisadas, pregunta si hay que crear mas wordCounterThreads.
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {				
		int i = 0;
		int trabajosTotales=0;
		int cantWordCounterThread = 0;
		this.ready = false;
		IQueueWordProcessing cola = null;
		WordCounterWork trab = null;	
		this.finishedWorks = new LinkedBlockingQueue <Integer>();
		this.finishedWorks.add(0);
		
		if(this.opcion == 1){
			this.factorDeDivision = 50;
		}
		else if(this.opcion ==3){
			this.factorDeDivision = 30;
		}
		
		
		if(opcion ==2){
			trabajosTotales = 1;
			cantWordCounterThread = 1;
			cola = new ConcreteQueueWordCounter(cantWordCounterThread,this);
			trab = new WordCounterWork(filepath,opcion,this.URL,-1,-1,this.resultado,this.finishedWorks);
			cola.put(trab);
		}else{
			try {
				trabajosTotales = countStoredUrls();
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}
			
			cantWordCounterThread = calcularWordCounterThreadsNecesarios(trabajosTotales);
								
			cola = new ConcreteQueueWordCounter(cantWordCounterThread,this);
			
						
			
			while(i <trabajosTotales){
				trab = null;
				if(i + this.factorDeDivision > trabajosTotales){
					trab = new WordCounterWork(filepath,opcion,this.URL,i+1, trabajosTotales,this.resultado, this.finishedWorks);
				}
				else{
					trab = new WordCounterWork(filepath,opcion,this.URL,i+1, i+this.factorDeDivision,this.resultado,this.finishedWorks);
				}
				
				i = i + this.factorDeDivision;
				cola.put(trab);
			}
		}
		
		tiempoEspera = 300+trabajosTotales*10;
		
		while(cantWordCounterThread != this.finishedWorks.peek()){
			try {
				Thread.currentThread();
				Thread.sleep(tiempoEspera);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		if (opcion ==1){
			wordCounterResult();
		}else if(opcion == 3){
			repeatedWordCounterResult();
		}
		
		this.ready = true;
	}

	/**
	 * Suma todos los resultados de los hilos al terminar de contar las palabras repetidas
	 * en sus respectivos trabajos.
	 */
	private void repeatedWordCounterResult() {
				
		List<String> tempWords = new ArrayList<String>();
		String [] tempWordsSplitted = null;
		String [] resultSplitted = null;
		String tempWord = "";
		long suma = 0;
		
		for(String word: this.resultado){			
			resultSplitted = word.split(" ");
			suma = Integer.parseInt(resultSplitted[1]);
			
			for(int i = 0; i<tempWords.size();i++){
				tempWordsSplitted = tempWords.get(i).split(" ");				
			
				if(resultSplitted[0].equals(tempWordsSplitted[0])){
					suma = suma + Integer.parseInt(tempWordsSplitted[1]);
					tempWords.remove(i);
				}
			}
			
			tempWord = resultSplitted[0] + " "+ suma;	
			tempWords.add(tempWord);
		}
		
		this.resultado.clear();
		for(String word: tempWords){
			this.resultado.add(word);
		}		
	}
			
	/**
	 * Suma todos los resultados de los hilos al terminar de contar la cantidad total de palabras
	 * en sus respectivos trabajos.
	 */
	private void wordCounterResult() {
		long words = 0;
		
		for(String word : resultado){
			words = words + Integer.parseInt(word);
		}
		
		resultado.clear();
		this.resultado.add(""+words);		
	}

	/**
	 * Retorna el valor del tag ready.
	 * 
	 * @return el valor del tag ready, verdadero si los trabajos han terminado sus calculos, falso si no.
	 */
	public boolean isReady(){
		return this.ready;
	}
	
	/**
	 * Asigna el valor asignado por parametro al atributo opcion de la clase.
	 * 
	 * @param opcion valor que se desea asignar al atributo opcion de la clase.
	 */
	public void setOpcion(int opcion){
		this.opcion = opcion;
	}
	
	/**
	 * Asigna el valor asignado por parametro al atributo URL de la clase.
	 * 
	 * @param URL valor que se desea asignar al atributo URL de la clase.
	 */
	public void setURL(String URL){
		this.URL = URL;
	}
}


